#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>


using namespace std;

const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;

//全局命令行参数表
char* gargv[argvnum];
int gargc = 0;

int lastcode = 0;

//全局shell工作路径
char pwd[basesize];
char pwdenv[basesize]; //定义环境全局变量

//我的系统环境变量
char* genv[envnum];


// 与重定向有关的全局环境变量
#define NoneRedir   0
#define InputRedir  1
#define OutputRedir 2
#define AppRedir    3

int redir = NoneRedir;
char* filename = nullptr;

#define TrimSpace(pos) do{\
    while(isspace(*pos)) {\
        pos++;\
    }\
}while(0)


string GetUserName()
{
    string name = getenv("USER");
    return name.empty() ? "None" : name;
}

string GetHostName()
{
    string hostname= getenv("HOSTNAME");
    return hostname.empty() ? "None" : hostname;
}

string GetPwd()
{
    if(!getcwd(pwd, sizeof(pwd))) return "None";
    //修改环境变量
    snprintf(pwdenv, sizeof(pwdenv), "PWD=%s", pwd);
    putenv(pwdenv);

    return pwd;
    //string pwd = getenv("PWD");
    //return pwd.empty() ? "None" : pwd;
}

string LastDir()
{
    string curr = GetPwd();
    if (curr == "/" || curr == "None") return curr;

    size_t pos = curr.rfind("/");
    if (pos == std::string::npos) return curr;
    return curr.substr(pos + 1);
}

string MakeCommandLine()
{
    //[gan@localhost lesson16]$ 
    char command_line[basesize];
    //这里使用#来和本身的shell作区分
    snprintf(command_line, basesize, "[%s@%s %s]#", \
            GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
    return command_line;
}

void PrintCommandLine() //1.命令行提示符
{
    printf("%s", MakeCommandLine().c_str());
    fflush(stdout);
}

bool GetCommandLine(char command_buffer[], int size) //2.获取用户命令
{
    //我们认为：我们要将用户输入的命令行 当做一个完整的字符串
    char* result = fgets(command_buffer, size, stdin); //从标准输入流获取
    if (!result)
    {
        return false;
    }
    command_buffer[strlen(command_buffer) - 1] = 0; //将最后的 \n 覆盖

    //当用户直接输入回车时判断长度是否为0
    if(strlen(command_buffer) == 0) return false;
    return true;
}

void ResetCommandLine() //初始化
{
    //为保证安全每次命令行解析都对参数列表进行清空
    memset(gargv, 0, sizeof(gargv)); 
    gargc = 0;

    //重定向
    //每次执行完都重新把重定向重置
    redir = NoneRedir;
    filename = nullptr;
}

void ParseRedir(char command_buffer[], int len) //分析重定向
{
    int end = len - 1;
    while(end >= 0)
    {
        if (command_buffer[end] == '<')
        {
            redir = InputRedir;
            command_buffer[end] = 0;
            filename = &command_buffer[end + 1];
            TrimSpace(filename);
            break;
        }
        else if (command_buffer[end] == '>')
        {
            if (command_buffer[end - 1] == '>')
            {
                //追加重定向
                redir = AppRedir;
                command_buffer[end] = 0;
                command_buffer[end - 1] = 0;
                filename = &command_buffer[end] + 1;
                TrimSpace(filename);
            } 
            else 
            {
                //输出重定向
                redir = OutputRedir;
                command_buffer[end] = 0;
                filename = &command_buffer[end + 1];
                TrimSpace(filename);
            }
            break;
        }

        end--;
    }
}

void ParseCommand(char command_buffer[]) //分析命令(没有重定向符号)
{
    const char* sep = " "; //定义分隔符
    gargv[gargc++] = strtok(command_buffer, sep); //对字符串切分

    while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
    //这里会多加一个1，所以自减1
    gargc--;
}

void ParseCommandLine(char command_buffer[], int len) //3.分析命令
{
    ResetCommandLine();  //初始化
    ParseRedir(command_buffer, len); //分析重定向
    ParseCommand(command_buffer);
}

void DoRedir() //执行重定向
{
    //1.重定向让子进程做
    //2. 程序替换会不会影响重定向？不会
    if (redir == InputRedir)
    {
        if (filename)
        {
            //此时文件一定要存在 输入重定向
            int fd = open(filename, O_RDONLY, 0666);
            if (fd < 0)
            {
                exit(2);
            }
            dup2(fd, 0);
        }
        else 
        {
            exit(1);
        }
    }
    else if (redir == OutputRedir)
    {
        if (filename)
        {
            int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0666);
            if (fd < 0)
            {
                exit(4);
            }
            dup2(fd, 1);
        }
        else 
        {
            exit(3);
        }
    }
    else if (redir == AppRedir) 
    {
        if (filename)
        {
            int fd = open(filename, O_CREAT | O_APPEND | O_WRONLY, 0666);
            if (fd < 0)
            {
                exit(6);
            }
            dup2(fd, 1);
        }
        else 
        {
            exit(5);
        }
    }
}

bool ExecuteCommand() //4.执行命令
{
    //创建子进程 让子进程执行命令
    pid_t id = fork();
    if (id < 0) return false;

    if (id == 0)
    {
        //子进程
        DoRedir();

        //1.执行命令
        //execvp(gargv[0], gargv);
        execvpe(gargv[0], gargv, genv);
        //2.退出
        exit(1);
    }

    int status = 0;
    pid_t rid = waitpid(id, &status, 0); //以阻塞等待方式
    if (rid > 0)
    {
        if(WIFEXITED(status))
        {
            lastcode = WEXITSTATUS(status);
        }
        else 
        {
            lastcode = 100;
        }
        return true;
    }
    return false;
}

void AddEnv(const char* item)
{
    int index = 0;
    while(genv[index++]);

    genv[--index] = (char*)malloc(strlen(item) + 1);
    strncpy(genv[index], item, strlen(item) + 1);
    genv[++index] = nullptr;
}

bool CheckAndExecBuiltCommand()
{
    if (strcmp(gargv[0], "cd") == 0) 
    {
        if (gargc == 2)
        {
            chdir(gargv[1]);
        }
        else 
        {
            lastcode = 1;
        }
        return true;
    }
    else if (strcmp(gargv[0], "export") == 0)
    {
        if (gargc == 2)
        {
            //export也是内键命令
            AddEnv(gargv[1]);
        } 
        else 
        {
            lastcode = 2;
        }
        return true;
    }
    else if (strcmp(gargv[0], "env") == 0)
    {
        if (gargc == 1)
        {
            for (int i = 0; genv[i]; ++i)
            {
                printf("%s\n", genv[i]);
            }
            lastcode = 0;
            return true;
        }
    }
    else if (strcmp(gargv[0], "echo") == 0)
    {
        if (gargc == 2)
        {
            //echo $?
            //echo $PATH
            //echo hello
            if (strcmp(gargv[1], "$?") == 0)
            {
                //打印退出码
                printf("%d\n", lastcode);
            }
            else 
            {
                printf("%s\n", gargv[1]);
            }
            lastcode = 0;
            return true;
        }
        else 
        {
            lastcode = 3;
        }
    }

    return false;
}

//初始化环境变量 
//作为一个shell获取环境变量应该从系统配置来
//我们今天就直接从父shell中获取环境变量
void InitEnv()
{
    //我们从父进程拷贝下来环境变量
    extern char** environ;
    int index = 0;
    while(environ[index]) 
    {
        genv[index] = (char*)malloc(strlen(environ[index]) + 1);
        strncpy(genv[index], environ[index], strlen(environ[index]) + 1);
        index++;
    }
    genv[index] = nullptr;
}

int main()
{
    //启动前初始化环境变量表
    InitEnv();
    char command_buffer[basesize];
    while(true)
    {
        PrintCommandLine(); //1.命令行提示符
        
        if (!GetCommandLine(command_buffer, basesize)) //2.获取用户命令
        {
            //此时接受的是false
            continue;
        }
        
        ParseCommandLine(command_buffer, strlen(command_buffer)); //3.分析命令

        //判断是否为内键命令
        if(CheckAndExecBuiltCommand())
        {
            continue;
        }

        ExecuteCommand(); //4.执行命令
    }
    return 0;
}
